package me.boot.auth.spring.config;

import com.google.common.collect.Lists;
import me.boot.auth.spring.AuthConst;
import me.boot.auth.spring.AuthMode;
import me.boot.auth.spring.handler.AuthHandler;
import me.boot.auth.spring.propertites.AuthProperties;
import me.boot.auth.spring.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

import java.util.List;

/**
 * File Name             :  SecurityConfig
 *
 * @author :  sylar
 * @create :  2018/11/15
 * Description           :
 * Reviewed By           :
 * Reviewed On           :
 * Version History       :
 * Modified By           :
 * Modified Date         :
 * Comments              :
 * CopyRight             : COPYRIGHT(c) me.iot.com   All Rights Reserved
 * *******************************************************************************************
 */
@Configuration
@EnableRedisHttpSession
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties({AuthProperties.class})
public class AuthConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    protected AuthProperties authProperties;
    @Autowired
    protected AuthHandler authHandler;
    @Autowired
    protected FilterInvocationSecurityMetadataSource metadataSource;
    @Autowired
    protected AccessDecisionManager accessDecisionManager;
    @Autowired
    protected UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private DaoAuthenticationProvider daoAuthenticationProvider;

    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题,此处只能放静态资源，后端的请求不可在此忽略
        //前后端分离开发模式，不用设置此项
        //web.ignoring().antMatchers("/**/css/*.css", "/static/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider);
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userService);
        authProvider.setPasswordEncoder(passwordEncoder);
        return authProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        DelegatingPasswordEncoder encoder = (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
        encoder.setDefaultPasswordEncoderForMatches(authProperties.getPasswordAlgorithm().getEncoder());
        return encoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).disable();
        http.headers().frameOptions().sameOrigin();

        if (authProperties.getAuthMode().equals(AuthMode.None)) {
            //匹配所有请求都不需要权限控制
            http.authorizeRequests().anyRequest().access("permitAll");
            return;
        }

        //自定义的权限验证过滤器，取代系统默认的XML式过滤器
        //使用自定义的过滤器后， @EnableGlobalMethodSecurity支持的3类注解都将无效
        http.formLogin()
                .loginPage(getLoginPage())
                .successHandler(authHandler)
                .failureHandler(authHandler)
                .permitAll();

        http.logout()
                .logoutUrl(getLogoutPage())
                .logoutRequestMatcher(new AntPathRequestMatcher(getLogoutPage()))
                .invalidateHttpSession(false)
                .logoutSuccessHandler(authHandler)
                .permitAll()
        ;
        http.exceptionHandling()
                .authenticationEntryPoint(authHandler)
                .accessDeniedHandler(authHandler)
        ;
        http.sessionManagement()
                //最大session并发数量
                .maximumSessions(authProperties.getMaximumSessions())
                //false之后登录踢掉之前登录,true则不允许之后登录
                .maxSessionsPreventsLogin(false)
                //登录被踢掉时的自定义操作
                .expiredSessionStrategy(authHandler);

        List<String> ignoreUrls = getIgnoreUrls();
        http.authorizeRequests()
                .antMatchers(ignoreUrls.toArray(new String[0])).permitAll()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(metadataSource);
                        o.setAccessDecisionManager(accessDecisionManager);
                        return o;
                    }
                });

    }

    protected String getLoginPage() {
        return authProperties.getLoginPage();
    }

    protected String getLogoutPage() {
        return authProperties.getLogoutPage();
    }

    public List<String> getIgnoreUrls() {
        return authProperties.getIgnoreUrls();
    }


}
